import { isElement } from 'share/utils'
import { MULTI_VALUE, RecordFromArrayLike } from './declare'

export default class RteIssets<T extends readonly string[]> {
  #data: Partial<RecordFromArrayLike<T, string>> = {}

  #cssText: string | null = null

  public size: number = 0

  public get data() {
    return { ...this.#data }
  }

  public set data(data) {
    Object.entries(data).forEach(([prop, value]) => this.set(prop, value as string))
  }

  public constructor(public props: T) {}

  public cssText() {
    if (this.#cssText === null) {
      this.#cssText = Object.entries(this.#data)
        .map(([prop, value]) => `${prop}: ${value};`)
        .join('')
    }
    return this.#cssText
  }

  /**
   * 从缓存中获取指定样式名的值
   * @param prop CSS Name
   * @returns CSS Value
   */
  public get(prop: string): string {
    return Reflect.get(this.#data, prop)
  }

  /**
   * 设置指定 CSS Name 的 CSS Value
   * @param prop CSS Name
   * @param value CSS Value
   * @param coverage 进行覆盖式赋值。true：覆盖式赋值；false：已存在值时忽略本次赋值。
   * @returns
   */
  public set(prop: string, value: string | number | null, coverage: boolean = true) {
    if (this.include(prop)) {
      if (value === null) {
        this.delete(prop)
      } else {
        value = `${value}`.trim()
        if (value.length) {
          let ovalue = this.get(prop)
          // 多个值共同组成 CSS Value
          if (MULTI_VALUE.includes(prop)) {
            const temp = new Set(ovalue ? ovalue.split(' ') : [])
            temp.add(value)
            value = [...temp].join(' ')
          }
          // 覆盖性赋值 || 新旧值不相等
          if (coverage || ovalue !== value) {
            Reflect.set(this.#data, prop, value)
            this.size = Object.keys(this.#data).length
          }
        }
      }
      this.#cssText = null
    }
    return this
  }

  /**
   * 判断指定 CSS Name 是否已赋值
   * @param prop CSS Name
   * @returns
   */
  public has(prop: string) {
    return Reflect.has(this.#data, prop)
  }

  /**
   * 判断指定 CSS Name 是否在允许范围内（是否被允许进行赋值缓存）
   * @param prop CSS Name
   * @returns
   */
  public include(prop: string) {
    return this.props.includes(prop)
  }

  /**
   * 移除 CSS Name
   * @param prop CSS Name
   * @returns
   */
  public delete(prop: string) {
    Reflect.deleteProperty(this.#data, prop)
    this.size = Object.keys(this.#data).length
    this.#cssText = null
    return this
  }

  /**
   * 如果 CSS Name 是组合式的值，则移除 CSS Name 项，否则移除 CSS Name
   * @param prop CSS Name
   * @param value CSS Value
   * @returns
   */
  public remove(prop: string, value: string | number) {
    if (MULTI_VALUE.includes(prop)) {
      if (this.has(prop)) {
        value = `${value}`.trim()
        const temp = new Set(this.get(prop).split(' '))
        temp.delete(value)
        if (temp.size) {
          return this.set(prop, [...temp].join(' '))
        }
        this.#cssText = null
      }
    }
    return this.delete(prop)
  }

  /**
   * 情况缓存
   * @returns
   */
  public clear() {
    this.#data = {}
    this.size = 0
    this.#cssText = null
    return this
  }

  /**
   * 将指定元素的 style property 同步到缓存中
   * @param target Node
   * @param coverage 覆盖性赋值
   * @returns
   */
  public from(target: any, coverage: boolean = true) {
    if (isElement(target)) {
      Array.from(target.style).forEach((prop) => {
        this.set(prop, target.style.getPropertyValue(prop), coverage)
      })
    } else if (coverage) {
      this.clear()
    }
    return this
  }

  /**
   * 将缓存中的数据同步到指定元素的 style property 上
   * @param target HTMLElement
   * @param coverage 是否为覆盖式赋值。true：覆盖式；false：合并式。
   */
  public to(target: HTMLElement, coverage: boolean = false) {
    coverage && target.removeAttribute('style')
    for (let key in this.#data) {
      target.style.setProperty(key, Reflect.get(this.#data, key))
    }
  }
}
